﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using XNADota.Terrain;
using System.IO;
using XNADota.ContentPipeline.Terrain;
using System.Diagnostics;
using System.Data;

namespace XNADota.GamePlay
{
    public enum EObjectType : int
    {
        UnitData,
        ItemData,
        DestructableData,
        DoodadData,
        AbilityData,
        //BuffData,
        UpgradeData,

        Num,
        None,
    }

    public class Profile
    {
        public Dictionary<string, string> ProfileValues;

        public Profile()
        {
            ProfileValues = new Dictionary<string, string>();
        }

        public Profile Copy()
        {
            Profile ret = new Profile();
            foreach (string key in ProfileValues.Keys)
            {
                ret.ProfileValues.Add(key, ProfileValues[key]);
            }
            return ret;
        }
    }

    public class ObjectTypeDictionary
    {
        private const string ErrorString = "$#Incorrect(Value!@#";

        private DataSet _MetaDataSet;
        private DataSet _OriginalDataSet;
        private DataSet _CustomDataSet;

        private Dictionary<string, Profile> _Profiles = new Dictionary<string, Profile>();

        public DataSet MetaDataSet
        {
            set { _MetaDataSet = value; }
            get { return _MetaDataSet; }
        }

        public DataSet OriginalDataSet
        {
            set { _OriginalDataSet = value; }
            get { return _OriginalDataSet; }
        }

        public DataSet CustomDataSet
        {
            set { _CustomDataSet = value; }
            get { return _CustomDataSet; }
        }

        public Dictionary<string, Profile> Profile
        {
            get { return _Profiles; }
        }

        public bool ContainId(string id)
        {
            DataTable table = _OriginalDataSet.Tables[0];
            DataRow[] rows = table.Select(table.Columns[0].ColumnName + "=" + QuerySring(id));
            if (rows.Length > 0) return true;

            table = _CustomDataSet.Tables[0];
            rows = table.Select(table.Columns[0].ColumnName + "=" + QuerySring(id));
            if (rows.Length > 0) return true;

            return false;
        }

        public string GetFieldValue(string id, string field)
        {
            for (int i = 0; i < _OriginalDataSet.Tables.Count; i++)
            {
                DataTable table = _OriginalDataSet.Tables[i];

                if (table.Columns.Contains(field))
                {
                    DataRow[] rows = table.Select(table.Columns[0].ColumnName + "=" + QuerySring(id));
                    if (rows.Length > 0)
                        return (string)rows[0][field];
                }
            }

            for (int i = 0; i < _CustomDataSet.Tables.Count; i++)
            {
                DataTable table = _CustomDataSet.Tables[i];

                if (table.Columns.Contains(field))
                {
                    DataRow[] rows = table.Select(table.Columns[0].ColumnName + "=" + QuerySring(id));
                    if (rows.Length > 0)
                        return (string)rows[0][field];
                }
            }
            return null;
        }

        #region constructor
        public void MergeProfile(string pathFile)
        {
            using (FileStream fileStream = new FileStream(pathFile, FileMode.Open, FileAccess.Read))
            {
                StreamReader reader = new StreamReader(fileStream);

                string line;
                Profile currentProfile = null;

                while (reader.EndOfStream == false)
                {
                    line = reader.ReadLine().Trim();

                    if (line.IndexOf("[") == 0 && line.IndexOf("]") == 5)
                    {
                        Debug.Assert(line.Length == 6);
                        line = line.Substring(1, 4);

                        if (_Profiles.ContainsKey(line))
                        {
                            currentProfile = _Profiles[line];
                        }
                        else
                        {
                            currentProfile = new Profile();
                            _Profiles.Add(line, currentProfile);
                        }
                    }
                    else
                    {
                        if (line.IndexOf("=") != -1)
                        {
                            string[] words = line.Split(new char[] { '=', '\0', '\t' }, StringSplitOptions.RemoveEmptyEntries);

                            switch (words.Length)
                            {
                                case 1:
                                    currentProfile.ProfileValues.Add(words[0], "");
                                    break;
                                case 2:
                                    if (currentProfile.ProfileValues.ContainsKey(words[0]))
                                    {
                                        currentProfile.ProfileValues.Remove(words[0]);
                                    }
                                    currentProfile.ProfileValues.Add(words[0], words[1]);
                                    break;
                                default:
                                    Debug.Assert(false);
                                    break;
                            }
                        }
                    }
                }
            }
        }

        public void ClearRows(DataSet dataSet)
        {
            for (int i = 0; i < dataSet.Tables.Count; i++)
                dataSet.Tables[i].Rows.Clear();
        }

        public bool CopyRow(DataSet originSet, DataSet customSet, string key, string newKey)
        {
            // Copy row with key in originSet into row in customSet with newKey
            // copy rows
            for (int index = 0; index < originSet.Tables.Count; index++)
            {
                DataTable table = originSet.Tables[index].Copy();
                string primaryKey = table.Columns[0].ColumnName;

                DataRow[] rows = table.Select(primaryKey + "=" + QuerySring(key));

                if (rows.Length == 1)
                {
                    Debug.Assert(rows.Length == 1);

                    rows[0][primaryKey] = newKey;
                    rows[0].AcceptChanges();

                    customSet.Tables[index].ImportRow(rows[0]);
                }
                else
                {
                    // Debug.WriteLine("not found key " + key);

                    return false;
                }
            }

            // copy profiles
            if (_Profiles.ContainsKey(key))
            {
                _Profiles.Add(newKey, _Profiles[key].Copy());
            }

            return true;
        }

        public void GetField(ObjectModifyProperty modifyProperty, out string field, out string value)
        {
            DataTable table = _MetaDataSet.Tables[0];
            DataRow[] rows = table.Select("id=" + QuerySring(modifyProperty.ModificationId));

            value = ErrorString;
            field = ErrorString;

            switch (modifyProperty.VariableType)
            {
                case 0:
                    value = modifyProperty.IntValue.ToString();
                    break;
                case 1:
                    value = modifyProperty.FloatValue.ToString();
                    break;
                case 2:
                    value = modifyProperty.FloatValue.ToString();
                    break;
                case 3:
                    value = modifyProperty.StringValue;
                    break;
                default:
                    Debug.Assert(false);
                    break;
            }

            if (rows.Length != 1)
            {
                //Debug.WriteLine(rows.Length + modifyProperty.ModificationId + " " + value);
            }
            else
            {
                Debug.Assert(rows.Length == 1);

                field = (string)rows[0]["field"];

                Debug.Assert(ErrorString.CompareTo("value") != 0);
            }
        }

        private string QuerySring(string key)
        {
            return "'" + key + "'";
        }

        public void ChangeField(DataSet dataSet, string key, ObjectModifyProperty modifyProperty)
        {
            string value;
            string field;
            GetField(modifyProperty, out field, out value);

            if (field.CompareTo(ErrorString) == 0 || value.CompareTo(ErrorString) == 0)
                return;

            bool changed = false;

            for (int tableIndex = 0; tableIndex < dataSet.Tables.Count; tableIndex++)
            {
                DataTable table = dataSet.Tables[tableIndex];

                // check if contain the column
                if (table.Columns.Contains(field))
                {
                    DataRow[] rows = table.Select(table.Columns[0].ColumnName + "=" + QuerySring(key));
                    if (rows.Length != 1)
                    {
                        //Debug.WriteLine(modifyProperty.ModificationId + " " + value);
                    }
                    else
                    {
                        Debug.Assert(rows.Length == 1);

                        rows[0][field] = value;
                        changed = true;
                    }

                    break;
                }
            }

            if (changed == false)
            {
                //Debug.WriteLine(field + " " + value);

                // it's must be in the profile files
                if (_Profiles.ContainsKey(key) == false)
                    _Profiles.Add(key, new Profile());
                if (_Profiles[key].ProfileValues.ContainsKey(field) == false)
                    _Profiles[key].ProfileValues.Add(field, "");
                _Profiles[key].ProfileValues[field] = value;
                //Debug.WriteLine(field + " " + value);
            }
            //Debug.Assert(changed);
        }
        #endregion
    }

    public class ObjectsManager
    {
        private Dictionary<EObjectType, ObjectTypeDictionary> _ObjectDictionary;

        private EObjectType GetTypeById(string id)
        {
            foreach (EObjectType type in _ObjectDictionary.Keys)
            {
                if (_ObjectDictionary[type].ContainId(id))
                    return type;
            }
            return EObjectType.None;
        }

        public BasicObject CreateObject(string id)
        {
            return CreateObject(id, GetTypeById(id));
        }

        public BasicObject CreateObject(string id, EObjectType type)
        {
            BasicObject result = null;

            switch (type)
            {
                case EObjectType.UnitData:
                    result = new UnitObject(id);
                    break;
                default:
                    throw new NotSupportedException("this type is not supported currently.");
            }

            return result;
        }

        public string GetFiledValue(EObjectType type, string id, string field)
        {
            string value = _ObjectDictionary[type].GetFieldValue(id, field);

            if (value == null && _ObjectDictionary[type].Profile.ContainsKey(id) && _ObjectDictionary[type].Profile[id].ProfileValues.ContainsKey(field))
                value = _ObjectDictionary[type].Profile[id].ProfileValues[field];

            return value;
        }

        #region constructor
        public ObjectsManager(Game game, LandscapeConfig landscapeConfig)
        {
            // Create Final Object Dictionary
            _ObjectDictionary = new Dictionary<EObjectType, ObjectTypeDictionary>();
            for (int index = 0; index < (int)EObjectType.Num; index++)
            {
                EObjectType type = (EObjectType)Enum.ToObject(typeof(EObjectType), index);
                _ObjectDictionary.Add(type, new ObjectTypeDictionary());
            }

            string path;

            #region load profile data
            List<string>[] profilePathNames = new List<string>[(int)(EObjectType.Num)];
            for (int index = 0; index < (int)EObjectType.Num; index++)
                profilePathNames[index] = new List<string>();

            profilePathNames[(int)EObjectType.UnitData].Add("HumanUnitFunc.txt");
            profilePathNames[(int)EObjectType.UnitData].Add("NeutralUnitFunc.txt");
            profilePathNames[(int)EObjectType.UnitData].Add("NightElfUnitFunc.txt");
            profilePathNames[(int)EObjectType.UnitData].Add("OrcUnitFunc.txt");
            profilePathNames[(int)EObjectType.UnitData].Add("UndeadUnitFunc.txt");

            profilePathNames[(int)EObjectType.ItemData].Add("ItemFunc.txt");

            profilePathNames[(int)EObjectType.AbilityData].Add("HumanAbilityFunc.txt");
            profilePathNames[(int)EObjectType.AbilityData].Add("NeutralAbilityFunc.txt");
            profilePathNames[(int)EObjectType.AbilityData].Add("NightElfAbilityFunc.txt");
            profilePathNames[(int)EObjectType.AbilityData].Add("OrcAbilityFunc.txt");
            profilePathNames[(int)EObjectType.AbilityData].Add("UndeadUnitFunc.txt");
            profilePathNames[(int)EObjectType.AbilityData].Add("ItemAbilityFunc.txt");

            profilePathNames[(int)EObjectType.UpgradeData].Add("HumanUpgradeFunc.txt");
            profilePathNames[(int)EObjectType.UpgradeData].Add("NeutralUpgradeFunc.txt");
            profilePathNames[(int)EObjectType.UpgradeData].Add("NightElfUpgradeFunc.txt");
            profilePathNames[(int)EObjectType.UpgradeData].Add("OrcUpgradeFunc.txt");
            profilePathNames[(int)EObjectType.UpgradeData].Add("UndeadUpgradeFunc.txt");

            for (int index = 0; index < (int)EObjectType.Num; index++)
            {
                EObjectType type = (EObjectType)Enum.ToObject(typeof(EObjectType), index);

                for (int i = 0; i < profilePathNames[index].Count; i++)
                {
                    path = Path.Combine(game.Content.RootDirectory + "\\units", profilePathNames[index][i]);

                    _ObjectDictionary[type].MergeProfile(path);
                }
            }
            #endregion

            #region load modify data
            // Create Object Dictionary

            // modify table declaration
            ObjectModifyFile _UnitModifyTable = null;
            ObjectModifyFile _ItemModifyTable = null;
            ObjectModifyFile _DestructableModifyTable = null;
            ObjectModifyFile _DoodadModifyTable = null;
            ObjectModifyFile _AbilityModifyTable = null;
            ObjectModifyFile _BuffModifyTable = null;
            ObjectModifyFile _UpgradeModifyTable = null;

            // load modify table
            string directory = Path.Combine(game.Content.RootDirectory, Path.GetDirectoryName(landscapeConfig.ConfigFilePath));
            path = Path.Combine(directory, landscapeConfig.WarCraftObjectsFileName);
            using (FileStream fileStream = new FileStream(path, FileMode.Open))
            {
                WCLoader Loader = new WCLoader(landscapeConfig.WarCraftPathFileName, fileStream);

                int _Version = Loader.ReadInt32();
                Debug.Assert(_Version == 1);

                if (Loader.ReadInt32() == 1)
                    _UnitModifyTable = new ObjectModifyFile(Loader, false);
                if (Loader.ReadInt32() == 1)
                    _ItemModifyTable = new ObjectModifyFile(Loader, false);
                if (Loader.ReadInt32() == 1)
                    _DestructableModifyTable = new ObjectModifyFile(Loader, false);
                if (Loader.ReadInt32() == 1)
                    _DoodadModifyTable = new ObjectModifyFile(Loader, true);
                if (Loader.ReadInt32() == 1)
                    _AbilityModifyTable = new ObjectModifyFile(Loader, true);
                if (Loader.ReadInt32() == 1)
                    _BuffModifyTable = new ObjectModifyFile(Loader, false);
                if (Loader.ReadInt32() == 1)
                    _UpgradeModifyTable = new ObjectModifyFile(Loader, true);

                Debug.Assert(Loader.Eof());
            }

            ObjectModifyFile[] modifyFiles =
            {
                _UnitModifyTable,
                _ItemModifyTable,
                _DestructableModifyTable,
                _DoodadModifyTable,
                _AbilityModifyTable,
                //_BuffModifyTable,
                _UpgradeModifyTable,
            };

            #endregion


            #region load meta data
            // Load meta data
            string[] metaDataFileName ={ 
                                           "units\\UnitMetaData.xlsx",
                                           "units\\UnitMetaData.xlsx",
                                           "units\\DestructableMetaData.xlsx",
                                           "Doodads\\DoodadMetaData.xlsx",
                                           "units\\AbilityMetaData.xlsx",
                                           //"units\\UpgradeEffectMetaData.xlsx",
                                           "units\\UpgradeMetaData.xlsx"};
            for (int index = 0; index < (int)EObjectType.Num; index++)
            {
                EObjectType type = (EObjectType)Enum.ToObject(typeof(EObjectType), index);
                SLKFile metaDataFile = new SLKFile(Path.Combine(game.Content.RootDirectory, metaDataFileName[index]));

                metaDataFile.CreateObjects(type, _ObjectDictionary[type], true, null);
            }
            #endregion

            #region load original data and apply changes
            // load original data
            string[] originalDataFileName = {
                                                "units\\UnitData.xlsx",
                                                "units\\ItemData.xlsx",
                                                "units\\DestructableData.xlsx",
                                                "Doodads\\Doodads.xlsx",
                                                "units\\AbilityData.xlsx",
                                                //"units\\AbilityData.xlsx",
                                                "units\\UpgradeData.xlsx",
                                            };
            for (int index = 0; index < (int)EObjectType.Num; index++)
            {
                EObjectType type = (EObjectType)Enum.ToObject(typeof(EObjectType), index);
                SLKFile originalDataFile = new SLKFile(Path.Combine(game.Content.RootDirectory, originalDataFileName[index]));

                // Unit data is created by multiple files
                if (type == EObjectType.UnitData)
                {
                    originalDataFile.Merge(Path.Combine(game.Content.RootDirectory, "units\\UnitUI.xlsx"));
                    originalDataFile.Merge(Path.Combine(game.Content.RootDirectory, "units\\UnitBalance.xlsx"));
                    originalDataFile.Merge(Path.Combine(game.Content.RootDirectory, "units\\UnitAbilities.xlsx"));
                    originalDataFile.Merge(Path.Combine(game.Content.RootDirectory, "units\\UnitWeapons.xlsx"));
                }

                originalDataFile.CreateObjects(type, _ObjectDictionary[type], false, modifyFiles[index]);
            }
            #endregion
        }
        #endregion
    }
}
